package site.sorghum.ddl.wood;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.noear.wood.DbContext;
import org.noear.wood.DbContextMetaData;
import site.sorghum.ddl.DDLDialect;
import site.sorghum.ddl.DDLDialectFactory;
import site.sorghum.ddl.base.BaseDdlWrapper;
import site.sorghum.ddl.entity.DdlColumnWrap;
import site.sorghum.ddl.entity.DdlIndexWrap;
import site.sorghum.ddl.entity.DdlTableWrap;
import site.sorghum.ddl.exception.DdlException;
import site.sorghum.ddl.extra.Convert;
import site.sorghum.ddl.extra.IgnoreHashMap;
import site.sorghum.ddl.proxy.ExeSqlProxy;

import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * WoodDdlWrapper
 *
 * @author Sorghum
 */
@Slf4j
public class WoodDdlWrapper implements BaseDdlWrapper {

    @Setter
    private DDLDialect dialect = null;

    private final DbContext dbContext;

    public WoodDdlWrapper(DbContext dbContext) {
        this.dbContext = dbContext;
        dbContext.initMetaData();
    }

    /**
     * 执行sql
     *
     * @param sql sql
     * @param ags 参数
     */
    public void exec(String sql, Object... ags) {
        if (ExeSqlProxy.exeSql(sql, ags).isForceReturn()) {
            return;
        }
        try {
            dbContext.sql(sql, ags).execute();
        } catch (SQLException e) {
            throw new DdlException(e);
        }
    }

    /**
     * 根据数据库连接信息获取DdlDialect
     *
     * @return DdlDialect
     */
    @Override
    public DDLDialect dialect() {
        if (dialect == null) {
            dialect = DDLDialectFactory.guess(dbContext.getMetaData().getDataSource());
        }
        return dialect;
    }

    /**
     * 根据数据库连接信息获取表结构信息
     *
     * @param dbContext 数据库连接
     * @param tableName 表名
     * @return 表结构信息
     */
    @Override
    public DdlTableWrap fromDataSource(String tableName) {
        return dialect().fromDataSource(
                schema(),
                tableName,
                this::sql2List,
                tableSql -> {
                    try {
                        Map<String, Object> map = dbContext.sql(tableSql).getMap();
                        IgnoreHashMap<Object> row = new IgnoreHashMap<>(map, IgnoreHashMap.IgnoreRule.CASE_INSENSITIVE_AND_TRIM);
                        DdlTableWrap ddlTableWrap = new DdlTableWrap();
                        ddlTableWrap.setName(tableName);
                        ddlTableWrap.setRemarks(Convert.toString(row.get("remarks")));
                        return ddlTableWrap;
                    } catch (SQLException e) {
                        throw new DdlException(e);
                    }
                },
                columnsSql -> {
                    try {
                        List<Map<String, Object>> mapList = dbContext.sql(columnsSql).getMapList();
                        return mapList.stream().map(
                                it -> {
                                    DdlColumnWrap ddlColumnWrap = new DdlColumnWrap();
                                    IgnoreHashMap<Object> row = new IgnoreHashMap<>(it, IgnoreHashMap.IgnoreRule.CASE_INSENSITIVE_AND_TRIM);
                                    ddlColumnWrap.setTable(tableName);
                                    ddlColumnWrap.setName(Convert.toString(row.get("name")));
                                    ddlColumnWrap.setDbType(Convert.toString(row.get("dbType")));
                                    ddlColumnWrap.setSize(Convert.toInt(row.get("size")));
                                    ddlColumnWrap.setDigit(Convert.toInt(row.get("digit")));
                                    ddlColumnWrap.setIsNullable(Convert.toBoolean(row.get("isNullable")));
                                    ddlColumnWrap.setDefaultValue(Convert.toString(row.get("defaultValue")));
                                    ddlColumnWrap.setRemarks(Convert.toString(row.get("remarks")));
                                    return ddlColumnWrap;
                                }
                        ).collect(Collectors.toList());
                    } catch (SQLException e) {
                        throw new DdlException(e);
                    }
                },
                indexSql -> {
                    try {
                        List<Map<String, Object>> mapList = dbContext.sql(indexSql).getMapList();
                        return mapList.stream().map(
                                it -> {
                                    DdlIndexWrap indexDdlWrap = new DdlIndexWrap();
                                    IgnoreHashMap<Object> row = new IgnoreHashMap<>(it, IgnoreHashMap.IgnoreRule.CASE_INSENSITIVE_AND_TRIM);
                                    indexDdlWrap.setTable(tableName);
                                    indexDdlWrap.setIndexName(Convert.toString(row.get("indexName")));
                                    indexDdlWrap.setUnique(Convert.toBoolean(row.get("isUnique")));
                                    String columnNames = Convert.toString(row.get("columnNames"));
                                    if (columnNames != null) {
                                        indexDdlWrap.setColumnNames(Arrays.stream(columnNames.split(",")).map(String::trim).collect(Collectors.toList()));
                                    }
                                    return indexDdlWrap;
                                }
                        ).collect(Collectors.toList());
                    } catch (SQLException e) {
                        throw new DdlException(e);
                    }
                }
        );
    }

    /**
     * 获取所有表名
     *
     * @return 所有表名
     */
    @Override
    public List<String> fetchAllTables() {
        return dialect().fetchAllTableNames(
                schema(),
                this::sql2List
        );
    }

    /**
     * 执行sql获取值
     *
     * @param sql sql
     * @return List
     */
    private List<String> sql2List(String sql) {
        List<Map<String, Object>> dataList = null;
        try {
            dataList = dbContext.sql(sql).getMapList();
            if (dataList.isEmpty()) {
                return Collections.emptyList();
            }
            Map<String, Object> first = dataList.get(0);
            if (first == null) {
                return Collections.emptyList();
            }
            return dataList.stream().map(
                            Map::values
                    ).map(it ->
                            it.iterator().next()).map(
                            it -> it == null ? null : it.toString())
                    .filter(Objects::nonNull).collect(Collectors.toList());
        } catch (SQLException e) {
            throw new DdlException(e);
        }
    }

    @Override
    public String schema() {
        DbContextMetaData metaData = dbContext.getMetaData();
        if (metaData.getSchema() != null) {
            return metaData.getSchema();
        }
        return metaData.getCatalog();
    }

}
